package net.jmchilton.spring.collect.spec;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import net.jmchilton.spring.collect.spec.namespace.Parameter;

/**
 * This class keeps track of different possible permutations to methods.
 *
 * For instance if a method was defined with parameters:
 *  
 *  <parameter type="Integer" minNum="0" maxNum="1" />
 *  <parameter type="String" minNum="1" maxNum="1" />
 * 
 * There are two possible permutations, (String) and (Integer,String).
 *   
 * An iterator over the possible parameter lists (String) and (Integer,String)
 * can be accessed via the iterator method.
 * 
 * 
 * @author John Chilton
 *
 */
class ParameterPermuations implements Iterable<List<Class<?>>> {
  private final Set<List<Class<?>>> permutations = new HashSet<List<Class<?>>>();
  
  ParameterPermuations(final List<Parameter> parameters) {
    this.permutations.add(new LinkedList<Class<?>>());
    for(final Parameter parameter : parameters) {
      final int minNum = Integer.parseInt(parameter.getMinNum());
      final int maxNum = Integer.parseInt(parameter.getMaxNum());
      final Class<?> parameterClass = ReflectionUtils.getClass(parameter.getType());
      if(minNum > maxNum) {
        throw new IllegalStateException("Invalid parameter definition, minNum > maxNum for element ");
      }
      for(int i = 0; i < minNum; i++) {
        this.addParameterType(parameterClass, false);
      }
      for(int i = minNum; i < maxNum; i++) {
        this.addParameterType(parameterClass, true);          
      }
    }
  }
  
  private void addParameterType(final Class<?> clazz, final boolean optional) {
    if(optional) {
      this.addOptionalParameterType(clazz);
    } else {
      this.addParameterType(clazz);
    }
  }

  /**
   * Adds an optional parameter, this entails keeping all of the
   * previous method possibilities and adding a new one for each
   * of the existing possibilities that ends with this parameter.
   * 
   * @param clazz
   */
  private void addOptionalParameterType(final Class<?> clazz) {
    final HashSet<List<Class<?>>> permutationsCopy = new HashSet<List<Class<?>>>(permutations);
    for(final List<Class<?>> classList : permutationsCopy) {
      final List<Class<?>> copyClassList = new ArrayList<Class<?>>(classList);
      copyClassList.add(clazz);
      this.permutations.add(copyClassList);
    }
  }

/**
 * Adds a required parameter, each possible method must
 * specify this parameter next.
 * 
 * @param clazz
 */
  private void addParameterType(final Class<?> clazz) {    
    for(final List<Class<?>> classList : this.permutations) {
      classList.add(clazz);
    }
  }

  /**
   * Returns a iterator over the possible permutations.
   */
  public Iterator<List<Class<?>>> iterator() {
    return this.permutations.iterator();
  }

}
